using System;
using System.IO;
using System.Collections;
using System.Threading;
using System.Diagnostics;

namespace Av
{
    /// <summary>
    /// An implementation of the Cron service.
    /// </summary>
    public class Cron
    {
        private ArrayList crontab;

        public Cron()
        {

            crontab = new ArrayList();

        }

        /// <summary>
        /// Returns array of task to be executed now or null
        /// </summary>
        public string[] FindTask()
        {
            return FindTask(DateTime.Now);
        }

        /// <summary>
        /// Returns array of task to be executed at specified time or null
        /// </summary>
        /// <param name="now">Datetime for task</param>
        public string[] FindTask( DateTime now )
        {
            // Is it better to make w/ ArrayList?
            string[] ar = new string[crontab.Count];
            int totalR = 0;

            foreach( ArrayList entry in crontab )
            {
                //Log4cs.Log(Importance.Debug, "Analizing task: {0}", entry[5].ToString());
                //Log4cs.Log("Month: {0}; mday: {1}; wday: {2}; hour: {3}; min: {4} = {5}", entry[0].ToString(), entry[1].ToString(), entry[2].ToString(), entry[3].ToString(), entry[4].ToString(), entry[5].ToString());
                if( Contains(entry[0], now.Month) &&
                        Contains(entry[1], GetMonthDay(now)) &&
                        Contains(entry[2], GetWeekDay(now)) &&
                        Contains(entry[3], now.Hour) &&
                        Contains(entry[4], now.Minute) )
                {
                    ar[totalR++] = entry[5].ToString();
                    //Log4cs.Log(entry[5].ToString());
                }
            }

            if( totalR > 0 )
                Array.Resize(ref ar, totalR);
            else
                ar = null;

            return ar;
        }


        // sort of a macro to keep the if-statement above readable
        private bool Contains( Object list, int val )
        {
            // -1 represents the star * from the crontab
            return ((ArrayList)list).Contains(val) || ((ArrayList)list).Contains(-1);
        }

        private int GetMonthDay( DateTime date )
        {
            date.AddMonths(-(date.Month - 1));
            return date.DayOfYear;
        }

        private int GetWeekDay( DateTime date )
        {
            if( date.DayOfWeek.Equals(DayOfWeek.Sunday) )
                return 7;
            else
                return (int)date.DayOfWeek;
        }


        /// <summary>
        /// Parses cron line to array. Format: minutes, hours, month day, month, week day, name of task
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public ArrayList ParseCronline( string line )
        {
            ArrayList minutes, hours, mDays, months, wDays;

            // re-escape space- and backslash-escapes in a cheap fashion
            line = line.Replace("\\\\", "<BACKSLASH>");
            line = line.Replace("\\ ", "<SPACE>");

            // split string on whitespace
            string[] cols = line.Split(new char[] { ' ', '\t' });

            for( int i = 0; i < cols.Length; i++ )
            {
                cols[i] = cols[i].Replace("<BACKSLASH>", "\\");
                cols[i] = cols[i].Replace("<SPACE>", " ");
            }

            if( cols.Length < 6 )
            {
                HandleError("Parse error in crontab (line too short).");
                crontab = new ArrayList();
            }

            minutes = ParseTimes(cols[0], 0, 59);
            hours = ParseTimes(cols[1], 0, 23);
            months = ParseTimes(cols[3], 1, 12);

            if( !cols[2].Equals("*") && cols[3].Equals("*") )
            {
                // every n monthdays, disregarding weekdays
                mDays = ParseTimes(cols[2], 1, 31);
                wDays = new ArrayList();
                wDays.Add(-1); // empty value
            } else if( cols[2].Equals("*") && !cols[3].Equals("*") )
            {
                // every n weekdays, disregarding monthdays
                mDays = new ArrayList();
                mDays.Add(-1); // empty value
                wDays = ParseTimes(cols[4], 1, 7); // 60 * 24 * 7
            } else
            {
                // every n weekdays, every m monthdays
                mDays = ParseTimes(cols[2], 1, 31);
                wDays = ParseTimes(cols[4], 1, 7); // 60 * 24 * 7
            }

            string args = "";

            for( int i = 6; i < cols.Length; i++ )
                args += " " + cols[i];

            ArrayList entry = new ArrayList(6);

            // Adding process arguments
            entry.Add(months);
            entry.Add(mDays);
            entry.Add(wDays);
            entry.Add(hours);
            entry.Add(minutes);
            entry.Add(cols[5]);
            entry.Add(args);

            return entry;
        }

        /// <summary>
        /// Parses array of Cron lines
        /// </summary>
        /// <param name="arLines"></param>
        public void ParseCronline( string[] arLines )
        {
            try
            {
                for( int i = 0; i < arLines.Length; i++ )
                {
                    if( string.IsNullOrEmpty(arLines[i]) || (arLines[i].StartsWith("#")) || (arLines[i].StartsWith(";")) )
                        continue;

                    ArrayList entry = ParseCronline(arLines[i]);

                    crontab.Add(entry);

                }
            } catch( Exception ex )
            {
                HandleError(ex.ToString());
            }
        }

        public void ParseCrontab( string filename )
        {
            StreamReader sr;

            try
            {
                string line;

                sr = new StreamReader(filename);

                while( (line = sr.ReadLine()) != null )
                {
                    line = line.Trim();

                    if( line.Length == 0 || line.StartsWith("#") || line.StartsWith(";") )
                        continue;

                    ArrayList entry = ParseCronline(line);

                    crontab.Add(entry);
                }  // END WHILE

                sr.Close();
            } catch( Exception e )
            {
                HandleError(e.ToString());
            }
        }

        public ArrayList ParseTimes( string line, int startNr, int maxNr )
        {
            ArrayList vals = new ArrayList();
            string[] list, parts;

            //Log4cs.Log("Parsing times: " + line);
            list = line.Split(new char[] { ',' });

            foreach( string entry in list )
            {
                int start, end, interval;

                //Log4cs.Log("Parsing time from: " + entry);
                parts = entry.Split(new char[] { '-', '/' });

                if( parts[0].Equals("*") )
                {
                    if( parts.Length > 1 )
                    {
                        start = startNr;
                        end = maxNr;

                        interval = int.Parse(parts[1]);
                    } else
                    {
                        // put a -1 in place
                        start = -1;
                        end = -1;
                        interval = 1;
                    }
                } else
                {
                    // format is 0-8/2
                    start = int.Parse(parts[0]);
                    end = parts.Length > 1 ? int.Parse(parts[1]) : int.Parse(parts[0]);
                    interval = parts.Length > 2 ? int.Parse(parts[2]) : 1;
                }

                for( int i = start; i <= end; i += interval )
                {
                    //Log4cs.Log(i.ToString());
                    vals.Add(i);
                }
            }
            return vals;
        }

        public void HandleError( string error )
        {
            Log4cs.Log(Importance.Error, error);
        }

        internal void Test()
        {
            for( int i = 0; i < crontab.Count; i++ )
            {
                ArrayList v = (ArrayList)((ArrayList)crontab[i])[0];
                for( int j = 0; j < v.Count; j++ )
                {
                    Log4cs.Log(v[i].ToString());
                }
            }
        }

        public override string ToString()
        {
            string s = "";

            foreach( ArrayList entry in crontab )
            {
                s += string.Format("Task {0} executes\n", entry[5].ToString());
                s += "\tMonth: ".PadRight(16, ' ');
                for( int i = 0; i < ((ArrayList)entry[0]).Count; i++ )
                    s += (i == 0) ? ((ArrayList)entry[0])[i].ToString() : ", " + ((ArrayList)entry[0])[i].ToString();

                s += "\n\tMonth day: ".PadRight(16, ' ');
                for( int i = 0; i < ((ArrayList)entry[1]).Count; i++ )
                    s += (i == 0) ? ((ArrayList)entry[1])[i].ToString() : ", " + ((ArrayList)entry[1])[i].ToString();

                s += "\n\tWeek day: ".PadRight(16, ' ');
                for( int i = 0; i < ((ArrayList)entry[2]).Count; i++ )
                    s += (i == 0) ? ((ArrayList)entry[2])[i].ToString() : ", " + ((ArrayList)entry[2])[i].ToString();

                s += "\n\tHours: ".PadRight(16, ' ');
                for( int i = 0; i < ((ArrayList)entry[3]).Count; i++ )
                    s += (i == 0) ? ((ArrayList)entry[3])[i].ToString() : ", " + ((ArrayList)entry[3])[i].ToString();

                s += "\n\tMinutes: ".PadRight(16, ' ');
                for( int i = 0; i < ((ArrayList)entry[4]).Count; i++ )
                    s += (i == 0) ? ((ArrayList)entry[4])[i].ToString() : ", " + ((ArrayList)entry[4])[i].ToString();
                s += "\n";

            }  // END FOREACH

            return s;
        }

    }
}
